package alibaba

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"strings"

	"github.com/bmatcuk/doublestar/v4"
	"golang.aliyun-inc.com/agit/agit/proc-receive-hook/config"
	"golang.aliyun-inc.com/agit/agit/proc-receive-hook/git"
	"golang.aliyun-inc.com/agit/agit/proc-receive-hook/protocol"
	log "golang.aliyun-inc.com/agit/alog/alogcore"
)

const (
	AGitTrunkModeKey = "TRUNK_MODE"
	AGitAllowPushKey = "ALLOW_PUSH"
	UseCodeReviewKey = "USE_CODE_REVIEW"
	ProjectIDKey     = "GL_REPOSITORY_ID"
	BranchPrefix     = "refs/heads/"
	TagPrefix        = "refs/tags/"
)

// CommandRouter route push commands to corresponding handler
type CommandRouter interface {
	routeCommand(context.Context, *protocol.Command, map[string]string, *RefReviewSetting) string
}

type CodeupRouter struct {
	detector AGitFlowDetector
}

/*
routeCommand 负责将命令路由至相应处理器
* 对于每一个command，可能被路由至以下之一的处理器：
* 1 agit 处理：处理用 git repo 推送的命令
* 2 推送评审处理：处理推送评审的命令，例如 -o review=new 创建评审
* 3 引用评审处理：处理引用的创建/删除/强推
* 4 放过：不处理command，直接向 receive-pack 汇报
具体路由的逻辑及顺序如下：
* 1. agit flow的ref, agit 处理
* 2. 指定了 review=no，放过
* 3. 分支/tag的创建删除，判断是否开启引用评审
* 4. 指定了除了no以外的 review 选项, 推送评审处理
* 5. 开启推送评审，允许代码推送，放过
* 6. 开启推送评审，推送评审处理
* 7. 以上未命中，放过
*/
func (r *CodeupRouter) routeCommand(ctx context.Context, command *protocol.Command, pushopts map[string]string, setting *RefReviewSetting) (method string) {
	defer func() {
		log.Info(ctx, "routeCommand",
			log.NewStringField("before", command.OldOID),
			log.NewStringField("after", command.NewOID),
			log.NewStringField("ref", command.Ref),
			log.NewStringField("route method", method))
	}()
	reviewVal, exist := pushopts["review"]
	trunkMode, _ := os.LookupEnv(AGitTrunkModeKey)
	allowPush, _ := os.LookupEnv(AGitAllowPushKey)

	if r.detector.IsAGitFlow(command.Ref) {
		return "agit-flow"
	}

	if exist && reviewVal == "no" {
		return "normal"
	}

	if r.isRefChange(ctx, command) {
		if setting != nil && setting.Enabled {
			if strings.HasPrefix(command.Ref, BranchPrefix) {
				if isMatch, _ := doublestar.Match(BranchPrefix+setting.BranchPattern, command.Ref); isMatch {
					return "ref"
				}
			}
			if strings.HasPrefix(command.Ref, TagPrefix) {
				if isMatch, _ := doublestar.Match(TagPrefix+setting.TagPattern, command.Ref); isMatch {
					return "ref"
				}
			}
		}
		return "normal"
	}

	if exist && reviewVal != "" {
		return "trunk-base"
	}

	if trunkMode == "true" && allowPush == "true" {
		return "normal"
	}

	if trunkMode == "true" {
		return "trunk-base"
	}

	return "normal"
}

// isRefChange 判断是否是引用的改动。新建引用/删除引用/回退分支需要创建引用评审
func (r *CodeupRouter) isRefChange(ctx context.Context, command *protocol.Command) bool {
	if r.detector.IsTags(command.Ref) {
		return true
	}
	if r.detector.IsHeads(command.Ref) && (command.OldOID == NullSHA1 || command.NewOID == NullSHA1) {
		return true
	}
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	err := git.MergeBase(ctx, &stdout, &stderr, []string{"--is-ancestor"}, command.NewOID, command.OldOID)
	outMsg := stdout.String()
	errMsg := stderr.String()
	if err == nil {
		return true
	}
	if exitErr, ok := err.(*exec.ExitError); ok {
		if exitErr.ExitCode() == 1 {
			return false
		}
	}

	log.Error(ctx, "run merge-base occurred error",
		log.NewStringField("error: ", err.Error()),
		log.NewStringField("stderr: ", errMsg),
		log.NewStringField("stdout:", outMsg))

	return false
}

func NewCodeupRouter(detector AGitFlowDetector) (router *CodeupRouter, err error) {
	if detector == nil {
		detector, err = NewGlobsDetector(config.Cfg.ProcReceiveRefs, config.Cfg.TagRefs, config.Cfg.HeadsRefs, config.Cfg.MergeRequestsRefs)
		if err != nil {
			return
		}
	}

	return &CodeupRouter{detector: detector}, nil
}
